package persistencia;

import interfaces.Notificador;

import java.util.List;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Iterator;

import vistas.PlanoEdificio;

import api.ControlReflection;
import api.ControlReflection.Atributo;
import api.ControlReflection.Metodo;
import api.Dispositivo;
import api.Regla;
import api.Regla.ObjetoAfectado;
import api.Respuesta;
import api.Suceso;

public class XMLmanager {
	
	private File archivo;
	
	private ControlReflection controlReflection;
	
	private List<Dispositivo> dispositivos;
	
	public XMLmanager(String pathArchivos){
		this.archivo = new File(pathArchivos);
		this.controlReflection = new ControlReflection();
		this.dispositivos = new ArrayList<Dispositivo>();
	}
	
	public XMLmanager(){
		this.archivo = new File("datos");
		this.controlReflection = new ControlReflection();
		this.dispositivos = new ArrayList<Dispositivo>();
	}

	//Persiste en disco los planos y reglas pasados por parametro
	public void serializar(List<PlanoEdificio> planos, List<Regla> reglas){
		try {
			DataOutputStream dos = new DataOutputStream(new FileOutputStream(this.archivo,true));
			this.serializarPlanos(planos, dos);
			this.serializarReglas(reglas, dos);
			dos.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	//Genera una lista de planos y una lista de reglas leidas del disco.
	//"planos" y "reglas" tienen que ser listas vacias pero inicializadas
	public void hidratar(List<PlanoEdificio> planos, List<Regla> reglas){
		if (planos != null && reglas != null){
			if (this.archivo.exists()){
				try {
					BufferedReader br = new BufferedReader(new FileReader(this.archivo));
					planos.addAll(this.hidratarPlanos(br));
					reglas.addAll(this.hidratarReglas(br));
					br.close();
				} catch (FileNotFoundException e) {
					e.printStackTrace();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	private void serializarDispositivo(Dispositivo disp,DataOutputStream dos) {
		try {
			dos.writeBytes("<Dispositivo " + "id=\"" + disp.getId() + "\">\n");
			dos.writeBytes("<coordX=\"" + disp.getCoordX() + "\"/>\n");
			dos.writeBytes("<coordY=\"" + disp.getCoordY() + "\"/>\n");
			dos.writeBytes("<piso=\"" + disp.getPiso() + "\"/>\n");
			dos.writeBytes("<pathImagen=\"" + disp.getPathImagen() + "\"/>\n");
			this.serializarNotificador(disp.getNotificador(), dos);
			dos.writeBytes("</Dispositivo>\n");
			this.dispositivos.add(disp);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	 
	private Dispositivo hidratarDispositivo(BufferedReader br){
		Dispositivo d = null;
		try {
			String linea = br.readLine();
			if (linea != null){
				int pos = linea.indexOf("id=");
				String id = linea.substring(pos+4, linea.length()-2);
				linea = br.readLine();
				int coordX = Integer.valueOf(linea.substring(9, linea.length()-3));
				linea = br.readLine();
				int coordY = Integer.valueOf(linea.substring(9, linea.length()-3));
				linea = br.readLine();
				String piso = linea.substring(7, linea.length()-3);
				linea = br.readLine();
				String pathImagen = linea.substring(13, linea.length()-3);
	
				Notificador n = this.hidratarNotificador(br);
				
				d = new Dispositivo(id,piso,pathImagen,n);
				d.setCoordenadas(coordX, coordY);
				linea = br.readLine(); //leo la linea de </dispositivo>
				this.dispositivos.add(d);
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return d;
	}
	 
	private void serializarNotificador(Notificador notificador,DataOutputStream dos) {
		try {
			@SuppressWarnings("rawtypes")
			Class claseN = notificador.getClass();
			List<Atributo> atributos = this.controlReflection.getAtributosPropios(claseN, false);
			dos.writeBytes("<Notificador>\n");
			dos.writeBytes("<clase=\"" + notificador.getClass().getName() + "\"/>\n");
			this.serializarAtributos(notificador, atributos, dos);
			dos.writeBytes("</Notificador>\n");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	 
	private Notificador hidratarNotificador(BufferedReader br) {
		Notificador n = null;
		try {
			String linea = br.readLine();
			
			if (linea.compareTo("<Notificador>") == 0){
				linea = br.readLine();
				
				String claseS = linea.substring(8,linea.length()-3);
				
				@SuppressWarnings("rawtypes")
				Class clase = Class.forName(claseS);
				n = (Notificador)clase.newInstance();
				linea = br.readLine();
				while (linea.compareTo("</Notificador>") != 0){
					String nombre = linea.substring(1, linea.indexOf("tipo=")-1);
					String tipo = linea.substring(linea.indexOf("tipo=")+6, linea.indexOf("valor=")-2);
					String valor = linea.substring(linea.indexOf("valor=")+7, linea.length()-3);
						
					this.settearValor(n,nombre,tipo,valor);
					
					linea = br.readLine();
				}
			}
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		return n;
	}
	
	private void serializarAtributos(Notificador notificador, List<Atributo> atributos,DataOutputStream dos) {
		try {
			Iterator<Atributo> itA = atributos.iterator();
			while (itA.hasNext()){
				Atributo a = itA.next();
				Field f = a.getAtributo();
				String linea = "<" + f.getName() + " tipo=\"" + f.getType().toString() +
				"\" valor=\"" + this.controlReflection.obtenerValor(notificador,a).toString() + "\"/>\n";
				dos.writeBytes(linea);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private void serializarRegla(Regla regla,DataOutputStream dos){
		try {
			dos.writeBytes("<Regla>\n");
			dos.writeBytes("<activa=\"" + regla.isActiva() + "\"/>\n");
			dos.writeBytes("<importancia=\"" + regla.getImportancia() + "\"/>\n");
			this.serializarRespuesta(regla.getRespuesta(),dos);
			this.serializarObjetosAfectados(regla.getObjetosAfectados(),dos);
			dos.writeBytes("</Regla>\n");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private Regla hidratarRegla(BufferedReader br){
		Regla regla = null;
		String importancia = "";
		try {
			String linea = br.readLine();
			
			if (linea.compareTo("<Regla>") == 0){
				
				linea = br.readLine();
				boolean activa = Boolean.valueOf(linea.substring(9, linea.length()-3));
				linea = br.readLine();
				if(linea.contains("Informativa")){
					importancia = "Informativa";
				}
				
				if (linea.contains("Critica")) {
					importancia = "Critica";
				}
				Respuesta r = this.hidratarRespuesta(br);
				ArrayList<ObjetoAfectado> oa = this.hidratarObjetosAfectados(br);
				
				regla = new Regla(r,oa);
				regla.setActivada(activa);
				regla.setImportancia(importancia);
				linea = br.readLine(); //leo </Regla>
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return regla;
	}
	
	private void serializarReglas(List<Regla> reglas,DataOutputStream dos){
		try {
			dos.writeBytes("<ListaReglas cantReglas=\"" + reglas.size() + "\">\n");
			for (int i = 0; i < reglas.size(); i++)
				this.serializarRegla(reglas.get(i), dos);
			dos.writeBytes("</ListaReglas>\n");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private List<Regla> hidratarReglas(BufferedReader br){
		List<Regla> reglas = null;
		
		try {
			reglas = new ArrayList<Regla>();
			
			String linea = br.readLine();

			int cantReglas = Integer.valueOf(linea.substring(25, linea.length()-2));
			
			for (int i = 0; i < cantReglas; i++)
				reglas.add(this.hidratarRegla(br));
			
			linea = br.readLine(); //leo </ListaReglas>
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return reglas;
	}
	
	private void serializarObjetosAfectados(List<ObjetoAfectado> objetosAfectados,DataOutputStream dos) {
		try {
			dos.writeBytes("<ListaObjetosAfectados cantObjetos=\"" + objetosAfectados.size() + "\">\n");
			for (int i = 0; i < objetosAfectados.size(); i++)
				this.serializarObjetoAfectado(objetosAfectados.get(i), dos);
			dos.writeBytes("</ListaObjetosAfectados>\n");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private ArrayList<ObjetoAfectado> hidratarObjetosAfectados(BufferedReader br) {
		ArrayList<ObjetoAfectado> objetos = null;
		
		try {
			objetos = new ArrayList<ObjetoAfectado>();
			
			String linea = br.readLine();
			
			int cantObjetos = Integer.valueOf(linea.substring(36, linea.length()-2));
			
			for (int i = 0; i < cantObjetos; i++)
				objetos.add(this.hidratarObjetoAfectado(br));
			
			linea = br.readLine(); //leo </ListaObjetosAfectados>
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return objetos;
	}
	
	private void serializarObjetoAfectado(ObjetoAfectado objetoAfectado, DataOutputStream dos) {
		try {
			dos.writeBytes("<ObjetoAfectado>\n");
			dos.writeBytes("<dispId=\"" + objetoAfectado.getId() + "\"/>\n");
			dos.writeBytes("<metodo=\"" + objetoAfectado.getMetodo().getName() + "\"/>\n");
			dos.writeBytes("</ObjetoAfectado>\n");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private ObjetoAfectado hidratarObjetoAfectado(BufferedReader br){
		ObjetoAfectado oa = null;
		
		try {
			String linea = br.readLine();
			
			if (linea.compareTo("<ObjetoAfectado>") == 0){
				
				linea = br.readLine();
				String idDisp = linea.substring(9, linea.length()-3);
				
				Notificador n = this.obtenerNotificador(idDisp);
				@SuppressWarnings("rawtypes")
				Class c = n.getClass();
				
				linea = br.readLine();
				String nombreMetodo = linea.substring(9, linea.length()-3);
				
				Metodo m = this.controlReflection.getMetodo(c, nombreMetodo);
				oa = new Regla().new ObjetoAfectado(idDisp,n, m);
				
				linea = br.readLine(); //leo </ObjetoAfectado>
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return oa;
	}
	
	//Devuelve el Notificador del dispositivo con id = idDsip
	private Notificador obtenerNotificador(String idDisp) {
		Notificador n = null;
		Iterator<Dispositivo> itD = this.dispositivos.iterator();
		boolean encontrado = false;
		while (itD.hasNext() && !encontrado) {
			Dispositivo d = itD.next();
			if (d.getId().compareTo(idDisp) == 0){
				n = d.getNotificador();
				encontrado = true;
			}
		}
		return n;
	}
	
	private void serializarRespuesta(Respuesta respuesta,DataOutputStream dos) {
		try {
			dos.writeBytes("<Respuesta>\n");
			dos.writeBytes("<descripcion=\"" + respuesta.getDescripcion() + "\"/>\n");
			dos.writeBytes("<consecutivos=\"" + respuesta.verificaConsecutivos() + "\"/>\n");
			dos.writeBytes("<cancelados=\"" + respuesta.verificaCancelados() + "\"/>\n");
			dos.writeBytes("<enOrden=\"" + respuesta.verificaOrden() + "\"/>\n");
			this.serializarSucesos(respuesta.getSucesosNecesarios(),dos);
			dos.writeBytes("</Respuesta>\n");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private Respuesta hidratarRespuesta(BufferedReader br) {
		Respuesta r = null;
		
		try {
			String linea = br.readLine();
			
			if (linea.compareTo("<Respuesta>") == 0){
				linea = br.readLine();
				String desc = linea.substring(14, linea.length()-3);
				linea = br.readLine();
				boolean cons = Boolean.valueOf(linea.substring(15, linea.length()-3));
				linea = br.readLine();
				boolean can = Boolean.valueOf(linea.substring(13, linea.length()-3));
				linea = br.readLine();
				boolean orden = Boolean.valueOf(linea.substring(10, linea.length()-3));
				
				List<Suceso> sucesosNecesarios = this.hidratarSucesos(br);
				
				linea = br.readLine(); //leo </Respuesta>;
				
				r = new Respuesta(desc,sucesosNecesarios,cons,can,orden);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return r;
	}
	
	private void serializarSucesos(List<Suceso> sucesosNecesarios,DataOutputStream dos) {
		try {
			int cantSucesos = sucesosNecesarios.size();
			dos.writeBytes("<ListaSucesos cantSucesos=\"" + cantSucesos + "\">\n");
			for (int i = 0; i < cantSucesos; i++){
				dos.writeBytes("<suceso=\"" + sucesosNecesarios.get(i).getDescripcion() + "\"/>\n");
			}
			dos.writeBytes("</ListaSucesos>\n");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private List<Suceso> hidratarSucesos(BufferedReader br){
		List<Suceso> resultado = null;
		try {
			resultado = new ArrayList<Suceso>();
			
			String linea = br.readLine();
			
			int cantSucesos = Integer.valueOf(linea.substring(27, linea.length()-2));
			for (int i = 0; i < cantSucesos; i++){
				linea = br.readLine();
				String desc = linea.substring(9, linea.length()-3);
				resultado.add(new Suceso(desc));
			}
	
			linea = br.readLine(); //leo </listSucesos>
		} catch (IOException e) {
			e.printStackTrace();
		}
		return resultado;
	}
	
	private void serializarPlano(PlanoEdificio plano,DataOutputStream dos) {
		try {
			dos.writeBytes("<Piso id=\"" + plano.getId() + "\">\n");
			dos.writeBytes("<pathImagen=\"" + plano.getPathImagen() + "\"/>\n");
			this.serializarDispositivos(plano.getDispositivos(),dos);
			dos.writeBytes("</Piso>\n");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private PlanoEdificio hidratarPlano(BufferedReader br){
		PlanoEdificio pe = null;
		try {
			String linea = br.readLine();
			if (linea != null){
				int pos = linea.indexOf("id=");
				String id = linea.substring(pos+4, linea.length()-2);
				linea = br.readLine();
				String pathImagen = linea.substring(13, linea.length()-3);
	
				List<Dispositivo> dispositivos = this.hidratarDispositivos(br);
	
				pe = new PlanoEdificio(id,pathImagen);
				
				Iterator<Dispositivo> itD = dispositivos.iterator();
				while (itD.hasNext()) pe.agregarDispositivo(itD.next());
				
				linea = br.readLine(); //leo la linea de </piso>
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return pe;
	}
	
	private void serializarPlanos(List<PlanoEdificio> planos,DataOutputStream dos) {
		try {
			dos = new DataOutputStream(new FileOutputStream(this.archivo));
			dos.writeBytes("<ListaPlanos cantPlanos=\"" + planos.size() + "\">\n");
			for (int i = 0; i < planos.size(); i++){
				this.serializarPlano(planos.get(i),dos);
			}
			dos.writeBytes("</ListaPlanos>\n");
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private List<PlanoEdificio> hidratarPlanos(BufferedReader br){
		List<PlanoEdificio> resultado = null;
		try {
			resultado = new ArrayList<PlanoEdificio>();

			String linea = br.readLine();
			int cantPlanos = Integer.valueOf(linea.substring(25, linea.length()-2));
			for (int i = 0; i < cantPlanos; i++) resultado.add(this.hidratarPlano(br));
	
			linea = br.readLine(); //leo </ListaPlanos>
		} catch (IOException e) {
			e.printStackTrace();
		}
		return resultado;
	}
	
	private void serializarDispositivos(List<Dispositivo> dispositivos,DataOutputStream dos) {
		try {
			dos.writeBytes("<ListaDisp cantDispositivos=\"" + dispositivos.size() + "\">\n");
			Iterator<Dispositivo> itD = dispositivos.iterator();
			
			while (itD.hasNext()){
				Dispositivo d = itD.next();
				this.serializarDispositivo(d,dos);
			}
			dos.writeBytes("</ListaDisp>\n");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private List<Dispositivo> hidratarDispositivos(BufferedReader br){
		List<Dispositivo> resultado = null;
		try {
			String linea = br.readLine();
			int cant = Integer.valueOf(linea.substring(29, linea.length()-2));
			
			resultado = new ArrayList<Dispositivo>();
			for (int i = 0; i < cant; i++){
				resultado.add(this.hidratarDispositivo(br));
			}
			
			br.readLine(); //leo </lista>
		} catch (IOException e1) {
			e1.printStackTrace();
		}
		return resultado;
	}
	
	
	
	private void settearValor(Notificador n, String nombre, String tipo, String valor) {
		List<Atributo> atributos = this.controlReflection.getAtributosPropios(n.getClass(), false);
		
		Iterator<Atributo> itA = atributos.iterator();
		while (itA.hasNext()){
			Atributo a = itA.next();
	
			if (a.getName().compareTo(nombre) == 0){
				Object valorAtt = null;
				@SuppressWarnings("rawtypes")
				Class claseTipo = a.getAtributo().getType();
				String nombreAux = claseTipo.getSimpleName();
				
				if (nombreAux.compareTo("boolean") == 0 || nombreAux.compareTo("Boolean") == 0){
					valorAtt = new Boolean(valor);
				} else if (nombreAux.compareTo("int") == 0 || nombreAux.compareTo("Integer") == 0){
					valorAtt = new Integer(valor);
				} else if (nombreAux.compareTo("float") == 0 || nombreAux.compareTo("Float") == 0){
					valorAtt = new Float(valor);
				} else if (nombreAux.compareTo("double") == 0 || nombreAux.compareTo("Double") == 0){
					valorAtt = new Double(valor);
				} else if (nombreAux.compareTo("long") == 0 || nombreAux.compareTo("Long") == 0){
					valorAtt = new Long(valor);
				} else if (nombreAux.compareTo("char") == 0 || nombreAux.compareTo("Character") == 0){
					valorAtt = new Character(valor.charAt(0));
				} else if (nombreAux.compareTo("String") == 0){
						valorAtt = new String(valor);
					} else {
						valorAtt = claseTipo.cast(valor);
					}
					
					this.controlReflection.cambiarValor(n, a, valorAtt);
				}
			}
		}
	}
